from Clases.Memoria import Memoria
from Clases.Signal import Signal

##### Clase Abstracta###
class AlgoritoAjuste():
    
    def __init__(self):
        self.memoria = Memoria()
        self.bloquesLibres = [ [0,self.memoria.getMaxCelda()] ] #lista de lista de 2 elementos [[a,b],[c,d]]
        self.bloquesUsados= []#lista de lista de 2 elementos [[a,b],[c,d]] 
# no utilizamos el tipo tupla xq no se pueden modificar los elementos internos usamos 
#listas de 2 elementos .
        
        
    def espacioLibreEnMemoria(self, cantInstrucciones):
        #retorna un buleano
        return sum(self.listaEspaciosLibres()) >= cantInstrucciones

    def obtenerValorDeBloque(self,bloque,cant):
        return bloque[0] + cant
    
    
    #Pone en memoria la lista de instrucciones    
    def guardar(self, bloqueAUtilizar, listaInstrucciones):
        cont = 0
        while not len(listaInstrucciones) == cont :
            self.memoria.put(self.obtenerValorDeBloque(bloqueAUtilizar, cont), listaInstrucciones[cont])
            cont+=1
        bloqueUsadoNuevo = [bloqueAUtilizar[0],self.obtenerValorDeBloque(bloqueAUtilizar, cont-1)]
        print("ESTADO ANTES DE MODIFICAR LOS BLOQUES    ")
        print("el bloque de los libres  a utilizar :",bloqueAUtilizar)
        print("bloque usado nuevo a agregar :",bloqueUsadoNuevo)
        print("bloques Libres: ",self.bloquesLibres)
        print("bloques Usados: ",self.bloquesUsados)
        
        if bloqueAUtilizar == bloqueUsadoNuevo:
        # eliminar directamente de los bloques libres todo el bloque             
            self.eliminarBloque(bloqueAUtilizar, self.bloquesLibres)        
        else:
        #actualizarBloquesUsadosLibres
            bloqueAUtilizar[0] = self.obtenerValorDeBloque(bloqueAUtilizar,cont)

        self.agregarBloque(bloqueUsadoNuevo, self.getBloqueUsados())
        self.ordenarBloques()
        if not len (self.bloquesLibres) == 1:
            self.fusionarBloquesConsecutivosLibres()
        
        print("metodoGUARDAR DESPUES DE MODIFICAR ")
        print("bloques Libres: ",self.bloquesLibres)
        print("bloques Usados: ",self.bloquesUsados)
        
    def guardarPrograma(self,pid,listaInstrucciones,asignacionContinua,managerInterrupt):
        
        
        if self.espacioLibreEnMemoria(len(listaInstrucciones)):
            if self.guardarEnMemoria(pid, listaInstrucciones, asignacionContinua):
                print("se guardo el programa con exito")
            else:
                print("VOY A HACER COMPACTACION")
                self.doCompactacion(asignacionContinua)
                print("VOY A GUARDAR PROGRAMA",pid)
                self.guardarPrograma(pid, listaInstrucciones, asignacionContinua,managerInterrupt)
        else:
            print("VOY A HACER SWAP")
            managerInterrupt.interrupt(Signal.SWAP)
            print("VOY A GUARDAR PROGRAMA",pid)
            self.guardarPrograma(pid, listaInstrucciones, asignacionContinua,managerInterrupt)
            
    def dameDirBase(self,dirBloque,tipoBloques):
        return (tipoBloques[dirBloque])[0] 
    
    #precondicion : la lista de Bloques Ocupados tiene que estar ordenado (1,2), (3,8) etc... 
    def calcularDiferenciaEntreBloques(self):
        #Retorna: una lista de enteros con los espacios de diferencia entre cada bloques
        listResult = []
        contador =1
        listResult.append(self.bloquesUsados[0][0]) 
        ultimo = (self.getBloqueUsados()[0])[1]
        while not len(self.getBloqueUsados()) == contador : 
            listResult.append((self.getBloqueUsados()[contador])[0] - ultimo - 1)
            ultimo = self.getBloqueUsados()[contador][1]
            contador += 1
        
        return listResult
        
    def transladarBloque(self, asignacionContinua, bloque, diferencia):
        dirFisica = bloque[0] 
        
        #modificacion de los diccionarios tabla y tablaDirecciones
        pid = asignacionContinua.getTablaDirecciones().pop(dirFisica) #retorna el value que es un pid
        asignacionContinua.getTabla()[pid] = dirFisica-diferencia 
        asignacionContinua.getTablaDirecciones()[dirFisica+diferencia] = pid
        
        #subir cada instruccion del programa
        while bloque[1] != dirFisica:
            instruccion = self.memoria.read[dirFisica] #leer la memoria
            self.memoria.put(dirFisica - diferencia, instruccion)
            self.memoria.unload(dirFisica)
            dirFisica += dirFisica
            
        bloque[0]-=diferencia
        bloque[1]-=diferencia
    
    def doCompactacion(self,asignacionContinua):
        posicion = 0
        diferenciaEntreBloques = self.calcularDiferenciaEntreBloques()
        valor = diferenciaEntreBloques[posicion]
        while not len(self.bloquesUsados) > posicion:
            self.transladarBloque(asignacionContinua, self.bloquesUsados[posicion],valor)
            posicion += 1 
            valor += diferenciaEntreBloques[posicion] #check si se va de rango la lista
        
        #setea los bloques libres   
        primeraComponente = self.ultimoBloque(self.getBloqueUsados())[1]+1
        segundaComponente = self.memoria.getMaxCelda()
        
        self.bloquesLibres = [ [primeraComponente,segundaComponente] ]
        
        #recorre los bloques consecutivos
    def elMasConsecutivo(self,listaBloques,posicion):
        masConsecutivo =listaBloques[posicion][1]
        contador = 1
        hayConsecutivo = True
        while len(self.bloquesLibres) != posicion+1 and hayConsecutivo:
            if self.sonConsecutivos(listaBloques[posicion],listaBloques[posicion+1]):
                masConsecutivo= listaBloques[posicion+1] [1]
                contador+=1
                posicion+=1
            else:
                hayConsecutivo = False
                
        return [contador,masConsecutivo]

    #PARCIAL CON LISTA DE 1 SOLO BLOQUE
    def fusionarBloquesConsecutivosLibres(self):
        self.bloquesLibres
        listaAux = []
        posicionActual=0
        posicionSiguiente=1
        while len(self.bloquesLibres) != posicionSiguiente and  len(self.bloquesLibres) != posicionActual :
           
            if self.sonConsecutivos(self.bloquesLibres[posicionActual],self.bloquesLibres[posicionSiguiente]):
                tuplaResultado = self.elMasConsecutivo(self.bloquesLibres,posicionSiguiente) #me retorna un par(x,y)
                contador = tuplaResultado[0]
                bloqueNuevo = [self.bloquesLibres[posicionActual][0],tuplaResultado[1]]
                listaAux.append(bloqueNuevo)
                posicionActual+=contador
                posicionSiguiente+=contador
            else:
                listaAux.append(self.bloquesLibres[posicionActual])
            posicionActual+=1
            posicionSiguiente+=1
                
        
        self.bloquesLibres = listaAux
        
    def sonConsecutivos(self,posicionActual,posicionSiguiente):
        return posicionActual[1]+1 == posicionSiguiente[0]
        
    def getBloquesLibres(self):
        return self.bloquesLibres
    
    def getBloqueUsados(self):
        return self.bloquesUsados
    
    def calcularEspacio(self, tupla):
        #suponiendo que el tupla[1] > tupla[0]
        return tupla[1] - tupla[0] + 1  
    
    def listaEspaciosLibres(self):
        counter = 0
        listFreeSpace = []
        while  len(self.getBloquesLibres()) != counter:
            listFreeSpace.append(self.calcularEspacio(self.getBloquesLibres()[counter]))
            counter += 1
        return listFreeSpace

    def leerCelda(self,dirCelda):
        return self.memoria.read(dirCelda)
    
    def obtenerBloque(self,dirBase , listaBloques): #lista de bloques pueden ser bloquesUsados ,o bloquesLibres
        #le paso la 1ra componente de un bloque y me retorna el bloque completo. 
        result=0
        iterador = 0
        while  len(listaBloques) != iterador :
            if listaBloques[iterador] [0]  == dirBase:
            # a la lista le pido el elemento y al elemento la primera componente.
                result =listaBloques[iterador]
                break
            iterador+=1 
            
        return result
    
    def ultimoBloque(self,listaBloques):
        bloqueResultado = None
        for bloque in listaBloques:
            bloqueResultado = bloque
        return bloqueResultado
        
    def eliminarPrograma(self,dirBase):
        bloqueABorrar =self.obtenerBloque(dirBase, self.bloquesUsados)
        print("METODO ELIMINAR PROGRAMA")
        print("se va a eliminar el bloque", bloqueABorrar)
        self.memoria.unloadBlock(dirBase, self.calcularEspacio(bloqueABorrar))
        print("Estado Inicial de los bloques usados y libres")
        print("libres",self.bloquesLibres)
        print("usados",self.bloquesUsados)
        print("Elimino bloque de usados")
        self.eliminarBloque(bloqueABorrar, self.bloquesUsados)
        print("libres",self.bloquesLibres)
        print("usados",self.bloquesUsados)
        print("Agrego bloque a libres")
        self.agregarBloque(bloqueABorrar, self.bloquesLibres)
        if not len(self.bloquesLibres) ==1 :
            self.fusionarBloquesConsecutivosLibres()
        print("Estado Final de los bloques")
        print("libres",self.bloquesLibres)
        print("usados",self.bloquesUsados)
        print("FIN ELIMINAR PROGRAMA")
        
    def ordenarBloques(self):
        self.bloquesLibres =sorted(self.bloquesLibres )
        self.bloquesUsados=sorted(self.bloquesUsados)
        
    def eliminarBloque(self,bloque,listaBloques):
        #recorrer,eliminar y asegurarse de que este ordenada la lista
        listaBloques.remove(bloque)
        self.ordenarBloques()
    
    def agregarBloque(self,bloque, listaBloques):
        #recorrer,agregar y asegurarse de que este ordenada la lista
        listaBloques.append(bloque)
        self.ordenarBloques()
        
    def ponerEnDisco(self,dirFisica, disco ,pid,asignacion):
        #saca el programa de la memoria y lo coloca en el disco
        # la dirFisica es el mismo numero con el que comienzan los bloques usados
        bloque=self.obtenerBloque(dirFisica, self.getBloqueUsados())
        iterador=bloque [0]
        listaResult = []
        while iterador < bloque[1] +1:
            listaResult.append(self.memoria.read(iterador))
            iterador+=1
        self.memoria.unloadBlock(dirFisica, bloque[1]-bloque[0] )
        
        print("metodo poner En Disco ")
        print("Estado Inicial de los bloques usados y libres")
        print("libres",self.bloquesLibres)
        print("usados",self.bloquesUsados)
        print("EL BLOQUE QUE VOY A ELIMINAR",[dirFisica, bloque[1]])
        self.agregarBloque([dirFisica, bloque[1]], self.bloquesLibres)
        if not len (self.bloquesLibres) == 1:
            self.fusionarBloquesConsecutivosLibres()
        self.eliminarBloque([dirFisica, bloque[1]], self.bloquesUsados)
        
        print("Estado Final de los bloques usados y libres")
        print("libres",self.bloquesLibres)
        print("usados",self.bloquesUsados)
        asignacion.eliminarDireccionBase(pid)
        disco.guardarPrograma(pid,listaResult)
